#include <den/random/random.h>

#include <algorithm>
#include <chrono>
#include <limits>
#include <random>

#if defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW__)
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wsign-conversion"
    #pragma GCC diagnostic ignored "-Wconversion"

#endif

namespace Den
{
    namespace{

        std::default_random_engine& _RND()
        {
            static std::default_random_engine r(
                static_cast<uint32_t>(
                    std::chrono::system_clock::now().time_since_epoch().count()
                )
            );
            return r;
        }
        uint32_t RND() { return _RND()(); }

        template<typename T>
        inline T RandomI(const T rnd, T minvalue, T maxvalue)
        {
            if(minvalue>maxvalue) 
            {
                const T tmp = minvalue;
                minvalue = maxvalue;
                maxvalue = tmp;
            }
            return rnd % (maxvalue - minvalue + 1) + minvalue;
        }

        template<typename T>
        inline T RandomF(const T rnd, T minvalue, T maxvalue)
        {
            if(minvalue>maxvalue) 
            {
                const T tmp = minvalue;
                minvalue = maxvalue;
                maxvalue = tmp;
            }
            return rnd * (maxvalue - minvalue) + minvalue;
        }

        template<typename T> inline T ABS(const T v){ return (v>0)? v: -v; }
        

    }//namespace


//--------------------------------------------------------------------

    uint64_t GetRandomUI64()
    {
        const uint64_t result = RND();
        return (result << 32) | RND();
    }
    int64_t GetRandomI64()
    {
        const uint64_t temp = GetRandomUI64();
        return *reinterpret_cast<const uint64_t* const >(&temp);
    }
    uint32_t GetRandomUI32() { return RND(); }

    int32_t GetRandomI32()
    {
        const uint32_t temp = GetRandomUI32();
        return *reinterpret_cast<const int32_t* const>(&temp);
    }

    uint16_t GetRandomUI16()
    {
        return static_cast<uint16_t>(RND());
    }
    int16_t GetRandomI16()
    {
        const uint16_t temp = GetRandomUI16();
        return *reinterpret_cast<const int16_t* const>(&temp);
    }

    uint8_t GetRandomUI8() { return static_cast<uint8_t>(RND()); }

    int8_t GetRandomI8()
    {
        const uint8_t temp = GetRandomUI8();
        return *reinterpret_cast<const int8_t* const>(&temp);
    }

    float GetRandomF32()
    {
        return static_cast<float>(GetRandomUI32()) / std::numeric_limits<uint32_t>().max();
    }

    double GetRandomF64()
    {
        return static_cast<double>(GetRandomUI64()) / std::numeric_limits<uint64_t>().max();
    }
//--------------------------------------------------------------------------------------------

    uint64_t GetRandom(const uint64_t minvalue, const uint64_t maxvalue)
    {
        return RandomI( GetRandomUI64(), minvalue, maxvalue);
    }
    int64_t GetRandom(const int64_t  minvalue, const int64_t  maxvalue)
    {
        return RandomI( ABS(GetRandomI64()), minvalue, maxvalue);
    }
    uint32_t GetRandom(const uint32_t minvalue, const uint32_t maxvalue)
    {
        return RandomI(GetRandomUI32(), minvalue, maxvalue);
    }
    int32_t GetRandom(const int32_t  minvalue, const int32_t  maxvalue)
    {
        return RandomI(ABS(GetRandomI32()), minvalue, maxvalue);
    }
    uint16_t GetRandom(const uint16_t minvalue, const uint16_t maxvalue)
    {
        return RandomI(GetRandomUI16(), minvalue, maxvalue);
    }
    int16_t GetRandom(const int16_t minvalue, const int16_t maxvalue)
    {
        return RandomI(ABS(GetRandomI16()), minvalue, maxvalue);
    }
    uint8_t GetRandom(const uint8_t minvalue, const uint8_t maxvalue)
    {
        return RandomI(GetRandomUI8(), minvalue, maxvalue);
    }
    int8_t GetRandom(const int8_t minvalue, const int8_t maxvalue)
    {
        return RandomI( ABS(GetRandomI8()), minvalue, maxvalue);
    }
    double GetRandom(const double minvalue, const double maxvalue)
    {
        return RandomF(GetRandomF64(), minvalue, maxvalue);
    }
    float GetRandom(const float minvalue, const float maxvalue)
    {
        return RandomF(GetRandomF32(), minvalue, maxvalue);
    }
//--------------------------------------------------------------------------------------------
    uint64_t GetRandom(const uint64_t maxvalue) { return GetRandom( uint64_t (0), maxvalue); }
    int64_t  GetRandom(const int64_t  maxvalue) { return GetRandom( int64_t  (0), maxvalue); }
    uint32_t GetRandom(const uint32_t maxvalue) { return GetRandom( uint32_t (0), maxvalue); }
    int32_t  GetRandom(const int32_t  maxvalue) { return GetRandom( int32_t  (0), maxvalue); }
    uint16_t GetRandom(const uint16_t maxvalue) { return GetRandom( uint16_t (0), maxvalue); }
    int16_t  GetRandom(const int16_t  maxvalue) { return GetRandom( int16_t  (0), maxvalue); }
    uint8_t  GetRandom(const uint8_t  maxvalue) { return GetRandom( uint8_t  (0), maxvalue); }
    int8_t   GetRandom(const int8_t   maxvalue) { return GetRandom( int8_t   (0), maxvalue); }
    double   GetRandom(const double   maxvalue) { return GetRandom( double   (0), maxvalue); }
    float    GetRandom(const float    maxvalue) { return GetRandom( float    (0), maxvalue); }


}//namespace Den

#if defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW__)
    #pragma GCC diagnostic pop
#endif
